"""
通用方法。
提供一些枚举类型，块block类型,rect形状类型，文本方向，文本对齐方式
提供一些工具方法
"""

# 枚举类型
from enum import Enum
import random
from collections.abc import Iterable
from fitz.utils import getColorList, getColorInfoList


class BlockType(Enum):
    """块类型。"""

    # 未定义
    UNDEFINED = -1
    # 文本
    TEXT = 0
    # 内联图像
    IMAGE = 1
    # lattice格子表格
    LATTICE_TABLE = 2
    # 流式表格
    STREAM_TABLE = 3
    # 浮动图像
    FLOAT_IMAGE = 4


class RectType(Enum):
    """
    上下文中的形状类型。
    这种位运算定义的好处是：
    1. 每个枚举值都对应一个位，彼此之间不会重叠
    2. 可以通过按位或运算（|）来组合多个类型，例如 HIGHLIGHT | UNDERLINE = 0001 | 0010 = 0011
    3. 可以通过按位与运算（&）来检查某个组合值是否包含特定类型
    4. 这种设计在需要表示多个标志位的组合时非常有用，比如一个矩形可能同时具有高亮和下划线等多种属性。
    """

    # 高亮
    HIGHLIGHT = 1  # 二进制：0001
    # 下划线
    UNDERLINE = 1 << 1  # 二进制：0010 (1左移1位)
    # 删除线
    STRIKE = 1 << 2  # 二进制：0100 (1左移2位)
    # 超链接
    HYPERLINK = 1 << 3  # 二进制：1000 (1左移3位)
    # 边框
    BORDER = 1 << 4  # 二进制：10000 (1左移4位)
    # 阴影
    SHADING = 1 << 5  # 二进制：100000 (1左移5位)


class TextDirection(Enum):
    """文本方向。
    * LEFT_RIGHT: 行内从左到右，行与行从上到下
    * BOTTOM_TOP: 行内从下到上，行与行从左到右
    * MIX       : LEFT_RIGHT和BOTTOM_TOP的混合
    * IGNORE    : 既不是LEFT_RIGHT也不是BOTTOM_TOP
    """

    IGNORE = -1
    LEFT_RIGHT = 0
    BOTTOM_TOP = 1
    MIX = 2


class TextAlignment(Enum):
    """文本对齐方式。

    .. note::
        ``NONE``和``UNKNOWN``的区别：

        * NONE: 没有左/右/居中对齐 -> 需要TAB停止符
        * UNKNOWN: 无法确定，例如只有单行
    """

    NONE = -1
    UNKNOWN = 0
    LEFT = 1
    CENTER = 2
    RIGHT = 3
    JUSTIFY = 4


class IText:
    """考虑文本方向的文本相关接口。"""

    @property
    def text_direction(self):
        """默认文本方向是从左到右。"""
        return TextDirection.LEFT_RIGHT

    @property
    def is_horizontal_text(self):
        """检查文本方向是否从左到右。"""
        return (
            self.text_direction == TextDirection.LEFT_RIGHT
            or self.text_direction == TextDirection.MIX
        )

    @property
    def is_vertical_text(self):
        """检查文本方向是否从下到上。"""
        return self.text_direction == TextDirection.BOTTOM_TOP

    @property
    def is_mix_text(self):
        """检查文本方向是否既有从左到右也有从下到上。"""
        return self.text_direction == TextDirection.MIX


# 装饰器，类似于计算属性
# 使用方式：直接定义在一个计算的方法上@lazyproperty，类似于@property，但是@lazyproperty只会计算一次
# 适用于大量计算
# 访问属性类似于@property，不用执行，而是当成了一个属性来访问，直接 实例.属性
class lazyproperty:
    """
    只计算一次并缓存属性值。

    当使用 @lazyproperty 装饰一个方法时，该方法会作为 func 参数传递给 __init__

    第一次访问属性时，通过描述符 __get__ 方法计算值
    计算后，将结果以相同的名称存储在实例上
    后续访问时，由于实例上已有同名属性，Python 的属性查找机制会直接返回实例属性，不再调用描述符
    """

    def __init__(self, func):
        self.func = func

    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            value = self.func(instance)
            # 每个 Python 函数都有一个 __name__ 属性，它是函数名称的字符串表示
            # self.func.__name__ 就是获取被装饰方法的名称
            setattr(instance, self.func.__name__, value)
            return value


"""
使用示例：
class Person:
    def __init__(self, name):
        self.name = name
    
    @lazyproperty
    def greeting(self):
        print("计算问候语...")
        return f"你好，{self.name}！"

# 使用示例
p = Person("张三")

# 第一次访问 - 计算并缓存
print(p.greeting)  # 输出：计算问候语... 你好，张三！

# 内部实际发生了：
# p.greeting，发现p的__dict__中没有greeting
# 从Person类中找到greeting，发现被@lazyproperty装饰
# 找到lazyproperty的__get__方法，self, instance, cls分别是lazyproperty实例、p实例、Person类
# 因为instance不为None，所以执行value = self.func(instance)，即执行greeting方法(本质就是执行greeting方法，传入p实例)
# 计算后，将结果以__name__名称存储在p实例上

# 第二次访问 - 直接返回缓存值,因为p实例__dict__中已经有greeting
print(p.greeting)  # 输出：你好，张三！ (不再显示"计算问候语...")

# 验证属性已经变成了实例变量
print(p.__dict__)  # 输出：{'name': '张三', 'greeting': '你好，张三！'}

# 另外，直接访问Person.greeting返回的是描述符
print(Person.greeting)  # 输出：<__main__.lazyproperty object at 0x...>
# instance为None return self


"""


# -------------------------
# 方法
# -------------------------
def is_number(str_number):
    """判断是否可以转换为浮点数。"""
    try:
        float(str_number)
    except ValueError:
        return False
    else:
        return True


def flatten(items, klass):
    """
    从任何嵌套的可迭代对象中生成项目。

    参数:
        items (iterable): 任何嵌套的可迭代对象。
        klass (type): 如果遇到此类型的对象，则不展开。
    """
    for item in items:
        if isinstance(item, Iterable) and not isinstance(item, klass):
            yield from flatten(item, klass)
        else:
            yield item


def lower_round(number: float, ndigits: int = 0):
    """
    将数字舍入到指定位数的下限，例如 lower_round(1.26, 1)=1.2
    int()函数是向0取整
    参数:
        number (float): 要舍入的数字。
        ndigits (int): 舍入的位数。

    """
    n = 10.0**ndigits
    return int(n * number) / n


def decode(s: str):
    """尝试解码一个Unicode字符串。"""
    b = bytes(ord(c) for c in s)
    for encoding in ["utf-8", "gbk", "gb2312", "iso-8859-1"]:
        try:
            res = b.decode(encoding)
            break
        except:
            continue
    return res


# -------------------------
# 颜色方法
# -------------------------
def rgb_component_from_name(name: str = ""):
    """从fitz预定义颜色中获取命名的RGB颜色（或随机颜色），例如 'red' -> (1.0,0.0,0.0)。"""
    # 获取颜色索引
    if name and name.upper() in getColorList():
        pos = getColorList().index(name.upper())
    else:
        pos = random.randint(0, len(getColorList()) - 1)

    c = getColorInfoList()[pos]
    return (c[1] / 255.0, c[2] / 255.0, c[3] / 255.0)


def rgb_component(srgb: int):
    """srgb值转换为R,G,B分量，例如 16711680 -> (255, 0, 0)。

    等同于PyMuPDF内置方法::

        [int(255*x) for x in fitz.sRGB_to_pdf(x)]
    """
    # 十进制转十六进制: 0x...
    s = hex(srgb)[2:].zfill(6)
    return [int(s[i : i + 2], 16) for i in [0, 2, 4]]


def rgb_to_value(rgb: list):
    """RGB分量转换为十进制值，例如 (1,0,0) -> 16711680。"""
    res = 0
    for i, x in enumerate(rgb):
        res += int(x * (16**2 - 1)) * 16 ** (4 - 2 * i)
    return int(res)


def cmyk_to_rgb(c: float, m: float, y: float, k: float, cmyk_scale: float = 100):
    """CMYK分量转换为GRB值。"""
    r = (1.0 - c / float(cmyk_scale)) * (1.0 - k / float(cmyk_scale))
    g = (1.0 - m / float(cmyk_scale)) * (1.0 - k / float(cmyk_scale))
    b = (1.0 - y / float(cmyk_scale)) * (1.0 - k / float(cmyk_scale))
    res = rgb_to_value([r, g, b])  # type: int
    return res


def rgb_value(components: list):
    """灰度/RGB/CMYK模式分量转换为颜色值。"""
    num = len(components)
    # CMYK模式
    if num == 4:
        c, m, y, k = map(float, components)
        color = cmyk_to_rgb(c, m, y, k, cmyk_scale=1.0)
    # RGB模式
    elif num == 3:
        r, g, b = map(float, components)
        color = rgb_to_value([r, g, b])
    # 灰度模式
    elif num == 1:
        g = float(components[0])
        color = rgb_to_value([g, g, g])
    else:
        color = 0

    return color


# -------------------------
# pdf绘图
# -------------------------
def new_page(doc, width: float, height: float, title: str):
    """插入带有给定标题的新页面。

    参数:
        doc (fitz.Document): pdf文档对象。
        width (float): 页面宽度。
        height (float): 页面高度。
        title (str): 页面中显示的标题。
    """
    # 插入新页面
    page = doc.new_page(width=width, height=height)

    # 在左上角绘制标题
    gray = rgb_component_from_name("gray")
    page.insert_text((5, 16), title, color=gray, fontsize=15)

    return page


def debug_plot(title: str, show=True):
    """绘制内部函数返回的对象。

    参数:
        title (str): 页面标题。
        show (bool, optional): 如果show==False则不绘制。默认为True。

    .. note::
        内部函数的前提条件：
            - 第一个参数是 :py:class:`~pdf2docx.page.BasePage` 实例。
            - 最后一个参数是 ``dict`` 类型的配置参数。
    """

    def wrapper(func):
        def inner(*args, **kwargs):
            # 执行函数
            objects = func(*args, **kwargs)

            # 检查是否绘制页面
            page = args[0]  # BasePage对象
            debug = kwargs.get("debug", False)
            doc = kwargs.get("debug_doc", None)
            filename = kwargs.get("debug_filename", None)

            if show and objects and debug and doc is not None:
                # 创建新页面
                debug_page = new_page(doc, page.width, page.height, title)
                # 绘制对象，例如文本块、形状、表格...
                objects.plot(debug_page)
                doc.save(filename)

            return objects

        return inner

    return wrapper


def is_list_item(text, bullets=True, numbers=True):
    """如果`bullets`为真且`text`是一个项目符号字符，或者
    `numbers`为真且`text`不为空且完全由数字0-9组成，则返回`text`。
    否则返回None。

    如果`bullets`为True，我们使用内部项目符号字符列表；
    否则它应该是整数Unicode值的列表。
    """
    return False
    if bullets is True:
        bullets2 = (
            # From https://en.wikipedia.org/wiki/Bullet_(typography).
            0x2022,  # BULLET (&bull;, &bullet;)
            0x2023,  # TRIANGULAR BULLET
            0x2043,  # HYPHEN BULLET (&hybull;)
            0x204C,  # BLACK LEFTWARDS BULLET
            0x204D,  # BLACK RIGHTWARDS BULLET
            0x2219,  # BULLET OPERATOR for use in mathematical notation primarily as a dot product instead of interpunct.
            0x25C9,  # FISHEYE used in Japan as a bullet, and called tainome.
            0x25CB,  # WHITE CIRCLE (&cir;)
            0x25CF,  # BLACK CIRCLE
            0x25CF,  # Bullet, black small circle.
            0x25D8,  # INVERSE BULLET
            0x25E6,  # WHITE BULLET
            0x2619,  # REVERSED ROTATED FLORAL HEART BULLET; see Fleuron (typography)
            0x2765,  # ROTATED HEAVY BLACK HEART BULLET
            0x2767,  # ROTATED FLORAL HEART BULLET; see Fleuron (typography)
            0x29BE,  # CIRCLED WHITE BULLET (&olcir;)
            0x29BF,  # CIRCLED BULLET (&ofcir;)
            # Additional.
            0x25AA,  # Black small square, square bullet.
            0xF0B7,  # "Private Use Character" but seems to be used by libreoffice for bullets.
        )
    else:
        bullets2 = bullets
    if bullets:
        if len(text) == 1:
            c = text[0]
            cc = ord(c)
            if cc in bullets2:
                if bullets is True and cc == 0xF0B7:
                    return chr(0x2022)
                return text
    if numbers:
        for c in text:
            if isinstance(c, list):
                c = c[0]
            if c not in "0123456789":
                break
        else:
            return text
